package com.zhou.common.operator.log.core.core;


import com.google.common.collect.Maps;
import com.zhou.common.operator.log.core.annotation.OperateLog;
import com.zhou.common.operator.log.core.holder.OperatingInstructionAdditionalParamHolder;
import com.zhou.common.operator.log.core.model.OperateLogInfo;
import com.zhou.common.operator.log.core.model.PrincipalInfo;
import com.zhou.common.operator.log.core.resolver.ClientInfoResolver;
import com.zhou.common.operator.log.core.resolver.OperateResolver;
import com.zhou.common.operator.log.core.resolver.PrincipalResolver;
import com.zhou.common.operator.log.core.service.OperateLogStorageService;
import com.zhou.common.operator.log.core.util.JoinPointHelper;
import com.zhou.common.operator.log.core.util.SpringContextUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;

@Aspect
@Component
public class OperateLogAspect {
    private static final Logger log = LoggerFactory.getLogger(OperateLogAspect.class);
    @Resource
    private PrincipalResolver principalResolver;
    @Resource
    private OperateLogStorageService logService;
    @Resource
    private ClientInfoResolver clientInfoResolver;

    public OperateLogAspect() {
    }

    @Pointcut("@annotation(com.zhou.common.operator.log.core.annotation.OperateLog)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Throwable throwable = null;
        Object result = null;
        long startTime = System.currentTimeMillis();

        try {
            result = joinPoint.proceed();
        } catch (Throwable var8) {
            throwable = var8;
            throw var8;
        } finally {
            this.operateLogGatherAndProcess(joinPoint, result, throwable);
        }

        return result;
    }

    private void operateLogGatherAndProcess(ProceedingJoinPoint joinPoint, Object returnValue, Throwable exception) {
        try {
            try {
                OperateLog operateLog = JoinPointHelper.getOperateLog(joinPoint);
                if (operateLog != null) {
                    Object[] arguments = joinPoint.getArgs();
                    OperateLogInfo logInfo = this.createInstanceWithClientInfo();
                    PrincipalInfo principalInfo = this.principalResolver.resolvePrincipal(joinPoint, arguments);
                    logInfo.setPrincipal(principalInfo);
                    this.resolveOperate(logInfo, joinPoint, arguments, returnValue, exception);
                    String action = operateLog.action().getAction();
                    if (StringUtils.isNotEmpty(action) && StringUtils.isEmpty(logInfo.getAction())) {
                        logInfo.setAction(action);
                    }
                    logInfo.setOperatingSystem(operateLog.operateSystem());
                    String resource = operateLog.resource();
                    logInfo.setResource(resource);
                    setValueBySpel(operateLog, logInfo, joinPoint, returnValue, exception);
                    this.logService.append(logInfo);
                }
            } catch (Exception var15) {
                log.error("something bad happened when parse operate log,see details:{}", var15.getMessage());
            }

        } finally {
            ;
        }
    }

    /**
     * operatingInstruction,operatingType
     *
     * @param operateLog
     * @param logInfo
     * @return
     * @throws Exception
     */
    private void setValueBySpel(OperateLog operateLog, OperateLogInfo logInfo, ProceedingJoinPoint joinPoint,
            Object returnValue, Throwable exception) throws Exception {
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext evaluationContext = generateContext(joinPoint, returnValue, exception);
        String logDesc = operateLog.operateInstruction();
        if (StringUtils.isNotEmpty(logDesc) && StringUtils.isEmpty(logInfo.getOperatingInstruction())) {
            logInfo.setOperatingInstruction(parser.parseExpression(logDesc, new TemplateParserContext())
                    .getValue(evaluationContext, String.class));
        }
        if (StringUtils.isNotEmpty(operateLog.operateType())) {
            logInfo.setOperatingType(
                    parser.parseExpression(operateLog.operateType(), new TemplateParserContext())
                            .getValue(evaluationContext, String.class));
        }
        if (StringUtils.isNotEmpty(operateLog.operateModule()) && StringUtils.isEmpty(logInfo.getOperatingModule())) {
            logInfo.setOperatingModule(parser.parseExpression(operateLog.operateModule(), new TemplateParserContext())
                    .getValue(evaluationContext, String.class));
        }
    }

    public EvaluationContext generateContext(ProceedingJoinPoint joinPoint, Object returnValue, Throwable exception) {
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        String[] params = discoverer.getParameterNames(method);
        Object[] args = joinPoint.getArgs();
        EvaluationContext context = new StandardEvaluationContext();
        for (int len = 0; len < params.length; ++len) {
            context.setVariable(params[len], args[len]);
        }
        context.setVariable("resp", returnValue);
        context.setVariable("exception", exception);
        try {
            Map<String, Object> AdditionalParams = OperatingInstructionAdditionalParamHolder.getParams()
                    .or(Maps.newHashMap());
            AdditionalParams.forEach((k, v) -> {
                context.setVariable(k, v);
            });
        } finally {
            OperatingInstructionAdditionalParamHolder.clear();
        }
        return context;
    }


    private OperateLogInfo createInstanceWithClientInfo() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        OperateLogInfo logInfo = new OperateLogInfo();
        logInfo.setClientInfo(this.clientInfoResolver.resolveClientInfo(request));
        logInfo.setAddTime(new Date());
        return logInfo;
    }

    private void resolveOperate(OperateLogInfo logInfo, ProceedingJoinPoint joinPoint, Object[] args,
            Object returnValue, Throwable exception) {
        Class resolverClass = JoinPointHelper.getOperateLog(joinPoint).resolver();
        if (resolverClass != null) {
            OperateResolver resolver = (OperateResolver) SpringContextUtil.getBean(resolverClass);
            logInfo.setResource(resolver.resolveResource(joinPoint, args));
            logInfo.setPayloadInfo(resolver.resolvePayloads(joinPoint, args));
            logInfo.setRemark(resolver.resolveRemark(joinPoint, args));
            logInfo.setOperatingInstruction(resolver.resolveOperatingInstru(joinPoint, args));
            logInfo.setOperatingModule(resolver.resolveOperatingModule(joinPoint, args));
            logInfo.setAction(resolver.resolveAction(joinPoint, args));
        }

    }
}
